package com.google.appengine;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.maven.plugin.logging.Log;

public class DevAppServerClient implements AutoCloseable {

  private static Map<Integer, Connection> connections = new HashMap<Integer, Connection>();
  
  private Connection connection;
  
  public DevAppServerClient(int port) {
    connection = connections.get(port);
    if (connection == null)
      connection = new Connection(port); 
    connection.attach();
  }
  
  public boolean isAlive() {
    return connection.isAlive();
  }
  
  public boolean isServing(long id) {
    return connection.isServing(id);
  }
  
  public void start(long id, List<String> command, boolean synchronous, Log log) throws Exception {
    connection.start(id, command, synchronous, log);
  }
  
  public void stop(long id) throws UnknownHostException, IOException {
    connection.stop(id);
  }
  
  @Override
  public void close() {
    if (connection != null) {
      connection.detach();
      connection = null;
    }
  }
  
  private static class Connection {
    private final int port;
    
    private Socket socket;    
    private DataOutputStream outputStream;
    private DataInputStream inputStream;
    
    private Socket logSocket;
    private DataOutputStream logOutputStream;
    private DataInputStream logInputStream;
    
    private int refCount;
    
    public Connection(int port) {
      this.port = port;
      connections.put(port, this);
    }
    
    public synchronized void attach() {
      ++refCount;
    }
    
    public synchronized void detach() {
      if (--refCount < 1) {
        connections.remove(port);
        cleanup();
      }
    }
    
    public synchronized boolean isAlive() {
      try {
        connect();
        
        outputStream.writeByte(DevAppServerController.PING);
        byte status = inputStream.readByte();
        
        return status == DevAppServerController.OK;
      } catch (Exception e) {
        return false;
      }
    }
    
    public synchronized boolean isServing(long id) {
      try {
        connect();

        outputStream.writeByte(DevAppServerController.SERVING);
        outputStream.writeLong(id);
        byte status = inputStream.readByte();

        return status == DevAppServerController.OK;
      } catch (Exception e) {
        return false;
      }
    }
    
    public synchronized void start(long id, List<String> command, boolean synchronous, Log log) throws Exception {
      connect();
      
      outputStream.writeByte(DevAppServerController.START);
      outputStream.writeBoolean(synchronous);
      outputStream.writeInt(command.size());
      for (String str : command) {
        outputStream.writeUTF(str);
      }
      
      if (logSocket == null || logSocket.isClosed() || !logSocket.isConnected()) {
        startLogging(id, log);
      }
      
      byte status = inputStream.readByte();
      if (status != DevAppServerController.OK)
        throw new Exception("Error starting dev app server.");
    }
    
    public synchronized void stop(long id) throws UnknownHostException, IOException {
        connect();        
        outputStream.writeByte(DevAppServerController.STOP);
        inputStream.readByte();
    }
    
    private void startLogging(final long id, final Log log) throws UnknownHostException, IOException {
      logSocket = new Socket((String)null, port);
      logOutputStream = new DataOutputStream(socket.getOutputStream());
      logInputStream = new DataInputStream(socket.getInputStream());
      
      Thread logThread = new Thread("log-controller-devappserver") {
        @Override
        public void run() {
          try {
            logOutputStream.writeByte(DevAppServerController.LOG);
            logOutputStream.writeLong(id);
            
            while (logSocket.isConnected() && !Thread.interrupted()) {
              long id = logInputStream.readLong();
              byte stream = logInputStream.readByte();
              String line = logInputStream.readUTF();
              line = "Dev App Server " + id + ": " + line;
              
              if (stream == DevAppServerController.STDOUT)
                log.info(line);
              else
                log.error(line);
            }
          } catch (IOException e) {
            log.error(e.getMessage());
          }          
        }
      };
      logThread.start();
    }
    
    private void connect() throws UnknownHostException, IOException {
      if (socket == null || socket.isClosed() || !socket.isConnected()) {
        socket = new Socket((String)null, port);
        outputStream = new DataOutputStream(socket.getOutputStream());
        inputStream = new DataInputStream(socket.getInputStream());
      }
    }
    
    @Override
    protected void finalize() {
      cleanup();
    }
    
    private void cleanup() {
      try {
        if (outputStream != null)
          outputStream.writeByte(DevAppServerController.BYE);

        outputStream = null;
        inputStream = null;

        if (socket != null) {
          socket.close();
          socket = null;
        }
      } catch (IOException e) {
      }
    }      
  }
  
}
